Skip to content

http: call _writeRaw callback when destroyed#63447

Open
trivikr wants to merge 1 commit into
nodejs:mainfrom
trivikr:err-stream-destroyed
Open

http: call _writeRaw callback when destroyed#63447
trivikr wants to merge 1 commit into
nodejs:mainfrom
trivikr:err-stream-destroyed

Conversation

@trivikr
Copy link
Copy Markdown
Member

@trivikr trivikr commented May 20, 2026

OutgoingMessage._writeRaw() could return early without invoking the write
callback when the message, or its socket, was already destroyed.

This updates _writeRaw() to call the callback with ERR_STREAM_DESTROYED
in those cases, matching the existing destroyed write() behavior.

Fixes: #36673


Assisted-by: openai:gpt-5.5

Fixes: nodejs#36673

Signed-off-by: Kamat, Trivikram <16024985+trivikr@users.noreply.github.com>
Assisted-by: openai:gpt-5.5
@nodejs-github-bot
Copy link
Copy Markdown
Collaborator

Review requested:

  • @nodejs/http
  • @nodejs/net

@nodejs-github-bot nodejs-github-bot added http Issues or PRs related to the http subsystem. needs-ci PRs that need a full CI run. labels May 20, 2026
@codecov
Copy link
Copy Markdown

codecov Bot commented May 20, 2026

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 90.04%. Comparing base (0b5b189) to head (7c6b543).
⚠️ Report is 9 commits behind head on main.

Additional details and impacted files
@@            Coverage Diff             @@
##             main   #63447      +/-   ##
==========================================
- Coverage   90.06%   90.04%   -0.02%     
==========================================
  Files         714      714              
  Lines      225918   225928      +10     
  Branches    42734    42732       -2     
==========================================
- Hits       203464   203438      -26     
- Misses      14232    14275      +43     
+ Partials     8222     8215       -7     
Files with missing lines Coverage Δ
lib/_http_outgoing.js 96.16% <100.00%> (+0.36%) ⬆️

... and 27 files with indirect coverage changes

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@trivikr trivikr added author ready PRs that have at least one approval, no pending requests for changes, and a CI started. request-ci Add this label to start a Jenkins CI on a PR. labels May 20, 2026
@github-actions github-actions Bot removed the request-ci Add this label to start a Jenkins CI on a PR. label May 20, 2026
@nodejs-github-bot
Copy link
Copy Markdown
Collaborator

Copy link
Copy Markdown
Member

@pimterry pimterry left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Two small comments here, I could easily be persuaded to ignore these if that's complicated for some reason.

One larger issue though: with this PR in place, this code:

'use strict';
const http = require('http');
const { OutgoingMessage } = http;

const server = http.createServer((req, res) => {
  res.on('finish', () => console.log('finish'));
  res.on('error', (e) => console.log(`error:${e.code}`));
  res.on('close', () => {
    console.log('close');
    server.close();
    process.exit(0);
  });

  req.socket.destroy();
  console.log('aborted with destroy()');
  setImmediate(() => {
    // Write after socket destroyed:
    res.end('x');
    console.log('Wrote after socket destroyed');
  });
});

server.listen(0, '127.0.0.1', () => {
  http.get(`http://127.0.0.1:${server.address().port}/`).on('error', () => {});
});

Now prints:

aborted with destroy()
Wrote after socket destroyed
finish
close

I.e. with this change, we now emit 'finish' when ending aborted responses. Without this it skips finish and just closes.

I think that's because the onFinish callback used by end() ignores its error argument and just checks outmsg?.socket?._hadError, which isn't set here.

Comment thread lib/_http_outgoing.js

if (this.destroyed) {
if (typeof callback === 'function') {
process.nextTick(callback, new ERR_STREAM_DESTROYED('write'));
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In the equivalent cases elsewhere (like this) we do something like this.errored ?? new ERR_STREAM_DESTROYED('write') so that specific errors here are exposed if set, and ERR_STREAM_DESTROYED is just a fallback. Not strictly required but would nice imo.

Comment thread lib/_http_outgoing.js
// The socket was destroyed. If we're still trying to write to it,
// then we haven't gotten the 'close' event yet.
if (typeof callback === 'function') {
process.nextTick(callback, new ERR_STREAM_DESTROYED('write'));
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ditto, for errors from the socket itself.

Both could alternatively do { cause: this.errored } instead I suppose to expose underlying errors as a cause instead, if that's preferable.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

author ready PRs that have at least one approval, no pending requests for changes, and a CI started. http Issues or PRs related to the http subsystem. needs-ci PRs that need a full CI run.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

http: OutgoingMessage ERR_STREAM_DESTROYED callback

4 participants